/*
 * Copyright 2020 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "modules/audioplayer/SkAudioPlayer.h"

#include "include/core/SkData.h"
#include "oboe/Oboe.h"
#include "stream/MemInputStream.h"
#include "wav/WavStreamReader.h"

namespace {
class OboeAudioPlayer final : public SkAudioPlayer, oboe::AudioStreamCallback {
public:
    explicit OboeAudioPlayer(sk_sp<SkData> data)
        : fData(std::move(data)),
          fMemInputStream(const_cast<unsigned char *>(static_cast<const unsigned char *>(fData->data())),
        static_cast<int32_t>(fData->size()))
    {
        // wrap data in MemInputStream to parse WAV header
        fReader = std::make_unique<parselib::WavStreamReader>(&fMemInputStream);
        fReader->parse();
        // set member variables and builder properties using reader
        fNumSampleFrames = fReader->getNumSampleFrames();

        oboe::AudioStreamBuilder builder;
        builder.setPerformanceMode(oboe::PerformanceMode::LowLatency);
        builder.setSharingMode(oboe::SharingMode::Exclusive);
        builder.setSampleRate(fReader->getSampleRate());
        builder.setChannelCount(fReader->getNumChannels());
        builder.setCallback(this);
        builder.setFormat(oboe::AudioFormat::Float);

        // open the stream (must manually close it when done)
        fStream = nullptr;
        builder.openStream(fStream);
    }

private:
    oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames) override
    {
        // we assume float samples here
        float *outBuffer = static_cast<float *>(audioData);
        int framesRead = fReader->getDataFloat(outBuffer, numFrames);
        fReadFrameIndex += framesRead;
        int remainingFrames = numFrames - framesRead;
        if (remainingFrames > 0) {
            if (fIsLooping) {
                // handle wrap around
                fReader->positionToAudio();
                fReader->getDataFloat(&outBuffer[framesRead * fReader->getNumChannels()], remainingFrames);
                fReadFrameIndex += remainingFrames;
            } else {
                // render silence for rest
                renderSilence(&outBuffer[framesRead * fReader->getNumChannels()], remainingFrames);
                return oboe::DataCallbackResult::Stop;
            }
        }
        return oboe::DataCallbackResult::Continue;
    }

    void renderSilence(float *start, int numFrames)
    {
        for (int i = 0; i < numFrames * fReader->getNumChannels(); ++i) {
            start[i] = 0;
        }
    }
    double onGetDuration() const override
    {
        return fNumSampleFrames * fStream->getChannelCount() / fStream->getSampleRate();
    }

    double onGetTime() const override
    {
        return (fReadFrameIndex * fStream->getChannelCount()) / fStream->getSampleRate();
    }

    double onSetTime(double t) override
    {
        fReadFrameIndex = (t * fStream->getSampleRate()) / fStream->getChannelCount();
        return onGetTime();
    }

    State onSetState(State state) override
    {
        switch (state) {
            case State::kPlaying:
                fStream->start();
                break;
            case State::kStopped:
                fStream->close();
                break;
            case State::kPaused:
                fStream->pause();
                break;
        }

        return state;
    }


    // TODO: implement rate function (change sample rate of AudioStream)
    float onSetRate(float r) override
    {
        return r;
    }

    // TODO: implement volume function (multiply each sample by desired amplitude)
    float onSetVolume(float v) override
    {
        return v;
    }

    const sk_sp<SkData> fData;
    std::shared_ptr<oboe::AudioStream> fStream;
    std::unique_ptr<parselib::WavStreamReader> fReader;
    parselib::MemInputStream fMemInputStream;
    int32_t fReadFrameIndex{ 0 };
    int fNumSampleFrames;
    bool fIsLooping{ false };
};
} // namespace

std::unique_ptr<SkAudioPlayer> SkAudioPlayer::Make(sk_sp<SkData> src)
{
    return std::unique_ptr<SkAudioPlayer>(new OboeAudioPlayer(std::move(src)));
}
